#pragma once

#include <iostream>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include "../source/log/logger.hpp"
#include "../source/user.hpp"
#include "../source/matcher.hpp"
#include "../source/db/db.hpp"
#include "../source/room.hpp"
#include "../source/session.hpp"

#define WWWROOT "../wwwroot/"

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
class server
{
public:
    server(
        const std::string& host,
        const std::string& username,
        const std::string& password,
        const std::string& dbname,
        const unsigned int port = 3306,
        const std::string& wwwroot = WWWROOT)
        :_web_root(wwwroot),_ut(host,username,password,dbname,port),_rm(&_ut,&_ou)
        ,_mm(&_ut,&_ou,&_rm),_sm(&_wsserver)
    {
        /**
         * 基本初始化操作
         * 1和2是基本必须的初始化
         * 设置回调函数是websocketpp中的四个基本回调函数
         * 然后根据自己项目实现来完成
         * 在测试websocketpp中有介绍
         */
        //1.初始化日志等级--关闭,使用自己的日志函数
        _wsserver.set_access_channels(websocketpp::log::alevel::none);
        //2.初始化asio调试器
        _wsserver.init_asio();
        _wsserver.set_reuse_addr(true);
        //设置回调函数
        _wsserver.set_http_handler(std::bind(&server::http_callback,this,std::placeholders::_1));
        _wsserver.set_open_handler(std::bind(&server::wsopen_callback,this,std::placeholders::_1));
        _wsserver.set_close_handler(std::bind(&server::wsclose_callback,this,std::placeholders::_1));
        _wsserver.set_message_handler(std::bind(&server::wsmsg_callback,this,std::placeholders::_1,std::placeholders::_2));
        
    }
    //启动服务器
    void start(int port)
    {
        //设置监听端口号
        _wsserver.listen(port);
        //获取新连接
        _wsserver.start_accept();
        //启动服务器
        _wsserver.run();
    }
private:
    //静态资源请求处理
    void file_handler(wsserver_t::connection_ptr& conn)
    {
        //1.获取到conn中的资源路径
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        //2.组合出实际的资源路径
        std::string realpath = _web_root + uri;
        //3.判断资源路径请求是否合理(合理返回,不合理返回到登录页面)
        if(realpath.back() == '/')
            realpath += "login.html";
        //4.读取路径文件
        std::string body;
        bool ret = util_file_read::read_file(realpath,body);
        if(ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";
            body += "</head>";
            body += "<body>";
            body += "<h1> Not Found </h1>";
            body += "</body>";
            conn->set_status(websocketpp::http::status_code::not_found);
            conn->set_body(body);
            return;
        }
        //5.返回给用户静态网页
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
    }
    /**
     * 针对用户登录注册时的返回
     * conn:传入的web请求
     * result:返回情况(true,false)
     * code:返回的状态码
     * reason:返回给用户提示
    */
    void http_resp(wsserver_t::connection_ptr& conn,bool result,websocketpp::http::status_code::value code,const std::string& reason)
    {
        Json::Value resq_json;
        resq_json["result"] = result;
        resq_json["reason"] = reason;
        std::string resq_body;
        util_json::serialize(resq_json,resq_body);
        conn->set_body(resq_body);
        conn->set_status(code);
        conn->append_header("Content-Type", "application/json");
        return;
    }
    //注册请求处理
    void reg(wsserver_t::connection_ptr& conn)
    {
        //1.获取注册请求
        websocketpp::http::parser::request req = conn->get_request();
        //2.获取正文
        std::string body = conn->get_request_body();
        //3.对正文进行反序列化得到账号和密码
        Json::Value resq_json;
        bool ret = util_json::unserialize(body,resq_json);
        if(ret == false)
        {
            DBG_LOG("获取body反序列化失败!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求格式错误");
        }
        //4.调用数据库完成数据的添加
        if(resq_json["username"].isNull() || resq_json["password"].isNull())
        {
            DBG_LOG("请输入正确的账号或密码");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请输入正确的账号或密码");
        }
        ret = _ut.user_register(resq_json);
        if(ret == false)
        {
            DBG_LOG("导入数据失败!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"用户名已存在,请重新选择");
        }
        //5.返回响应 200--ok
        return http_resp(conn,true,websocketpp::http::status_code::ok,"注册成功");
    }
    //登录请求处理
    void login(wsserver_t::connection_ptr& conn)
    {
        //1.获取登录请求
        websocketpp::http::parser::request req = conn->get_request();
        //2.获取登录正文
        std::string body = conn->get_request_body();
        //3.反序列化获取到正文中的账号和密码
        Json::Value resq_json;
        bool ret = util_json::unserialize(body,resq_json);
        if(ret == false)
        {
            DBG_LOG("获取正文反序列化失败!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求格式错误");
        }
        //4.和数据库中进行对比
        if(resq_json["username"].isNull() || resq_json["password"].isNull())
        {
            DBG_LOG("请输入正确的账号或密码");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请输入正确的账号或密码");
        }
        ret = _ut.user_login(resq_json);
        if(ret == false)
        {
            DBG_LOG("用户名或密码错误!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"用户名或密码错误");
        }
        //5.给该用户创建对应的session信息并设置定时信息
        uint64_t uid = resq_json["id"].asUInt64();
        session_ptr sp = _sm.create_session(uid,LOGIN);
        if(sp.get() == nullptr)
        {
            DBG_LOG("会话创建失败!");
            return http_resp(conn,false,websocketpp::http::status_code::internal_server_error,"会话创建失败");
        }
        _sm.set_session_timer(sp->get_sid(),SESSION_TIMEOUT);
        //6.设置响应头部:Set-Cookie,将sessionid通过cookie返回
        std::string cookie_ssid = "SSID=" + std::to_string(sp->get_sid());
        conn->append_header("Set-Cookie",cookie_ssid);
        return http_resp(conn,true,websocketpp::http::status_code::ok,"登录成功");
    }
    /**
     * 获取cookie信息，
     * 每个cookie之间由"; "隔开,通过key和value构成
     * cookie_str:总的cookie串
     */
    bool get_cookie_val(const std::string& cookie_str,const std::string& key,std::string& val)
    {
        // Cookie: SSID=XXX; path=/; 
        //1. 以 ; 作为间隔，对字符串进⾏分割，得到各个单个的cookie信息
        std::string sep = "; ";
        std::vector<std::string> cookie_arr;
        util_split::split(cookie_str,sep,cookie_arr);
        for(auto str : cookie_arr)
        {
            //2. 对单个cookie字符串，以 = 为间隔进⾏分割，得到key和val
            std::vector<std::string> tmp_arr;
            util_split::split(str,"=",tmp_arr);
            if(tmp_arr.size() != 2) { continue; }
            if(tmp_arr[0] == key)
            {
                val = tmp_arr[1];
                return true;
            }
        }
    }
    //用户信息获取处理
    void info(wsserver_t::connection_ptr& conn)
    {
        //1.获取用户连接
        websocketpp::http::parser::request req = conn->get_request();
        //2.从请求信息中获取到cookie信息并提取id
        std::string cookie_str = conn->get_request_header("Cookie");
        if(cookie_str.empty())
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"找不到cookie信息,请重新登录");
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str,"SSID",ssid_str);
        if(ret == false)
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"找不到cookie信息,请重新登录");
        //3.通过sid查找该session
        session_ptr ssp = _sm.get_session_by_sid(std::stoi(ssid_str));
        if(ssp.get() == nullptr)
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"登录过期,请重新登录");
        //4.从会话中获取到用户id
        uint64_t uid = ssp->get_user();
        Json::Value user_info;
        ret = _ut.select_user_by_id(uid,user_info);
        if(ret == false)
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"找不到该用户信息,请重新登录");
        //5.获取该id的信息,json形式发给客户端
        std::string body;
        util_json::serialize(user_info,body);
        conn->set_body(body);
        conn->append_header("Content-Type", "application/json");
        conn->set_status(websocketpp::http::status_code::ok);
        //6.刷新定时间
        _sm.set_session_timer(ssp->get_sid(),SESSION_TIMEOUT);
    }
    //http请求处理
    void http_callback(websocketpp::connection_hdl hdl)
    {
        //通过hdl获取conn信息
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        //获取请求
        websocketpp::http::parser::request req = conn->get_request();
        //获取请求类型和路径信息
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        /**
         * 根据类型和路径选择处理
         * 分别是注册、登录、获取用户信息
         * 其它一律按照静态资源返回
         */
        if(method == "POST" && uri == "/reg")
            return reg(conn);
        else if(method == "POST" && uri == "/login")
            return login(conn);
        else if(method == "GET" && uri == "/info")
            return info(conn);
        else 
            return file_handler(conn);
    }
    //websocket响应信息
    void ws_resp(wsserver_t::connection_ptr conn,Json::Value& resp)
    {
        std::string body;
        util_json::serialize(resp,body);
        conn->send(body);
    }
    //通过cookie获取连接信息
    session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn)
    {
        //1.通过KEY=Cookie从conn中获取到cookie字段
        std::string cookie_str = conn->get_request_header("Cookie");
        //2.判断cookie信息是否正常
        Json::Value resq_json;
        if(cookie_str.empty())
        {
            resq_json["optype"] = "hall_ready";
            resq_json["reason"] = "未获取到cookie信息,需要重新登录";
            resq_json["result"] = false;
            ws_resp(conn,resq_json);
            return session_ptr();
        }
        //3.从cookie中获取到ssid
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str,"SSID",ssid_str);
        if(ret == false)
        {
            resq_json["optype"] = "hall_ready";
            resq_json["reason"] = "未获取到ssid信息,需要重新登录";
            resq_json["result"] = false;
            ws_resp(conn,resq_json);
            return session_ptr();
        }
        //4.通过ssid获取到对于的session
        session_ptr resq_sp = _sm.get_session_by_sid(std::stol(ssid_str));
        if(resq_sp.get() == nullptr)
        {
            resq_json["optype"] = "hall_ready";
            resq_json["reason"] = "未获取到session信息,需要重新登录";
            resq_json["result"] = false;
            ws_resp(conn,resq_json);
            return session_ptr();
        }
        return resq_sp;
    }
    //游戏大厅长连接
    void wsopen_game_hall(wsserver_t::connection_ptr& conn)
    {
        //1.获取用户信息
        Json::Value resq_json;
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;
        //2.判断该用户是否正常(不能重复登陆)
        if(_ou.user_in_hall(sp->get_user()) || _ou.user_in_room(sp->get_user()))
        {
            resq_json["optype"] = "hall_ready";
            resq_json["reason"] = "玩家重复登录！";
            resq_json["result"] = false;
            return ws_resp(conn,resq_json);
        }
        //3.将该用户添加到游戏大厅长连接
        _ou.get_game_hall(sp->get_user(),conn);
        //4.给客户端响应游戏大厅连接成功
        resq_json["optype"] = "hall_ready";
        resq_json["result"] = true;
        ws_resp(conn,resq_json);
        //5.设置会话定时
        _sm.set_session_timer(sp->get_sid(),SESSION_FOREVER);
    }
    //游戏房间长连接
    void wsopen_game_room(wsserver_t::connection_ptr& conn)
    {
        //1.获取用户数据
        Json::Value resq_json;
        session_ptr sp = get_session_by_cookie(conn); 
        if(sp.get() == nullptr)
            return;
        //2.判断该用户是否正常(不能重复登陆)
        if(_ou.user_in_hall(sp->get_user()) || _ou.user_in_room(sp->get_user()))
        {
            resq_json["optype"] = "room_ready";
            resq_json["reason"] = "玩家重复登录！";
            resq_json["result"] = false;
            return ws_resp(conn,resq_json);
        }
        //3.创建对战房间
        room_ptr rp = _rm.get_room_by_uid(sp->get_user());
        if(rp.get() == nullptr)
        {
            resq_json["optype"] = "room_ready";
            resq_json["reason"] = "没有找到玩家的房间信息";
            resq_json["result"] = false;
            return ws_resp(conn, resq_json); 
        }
        //4.将用户添加到该对战房间
        _ou.get_game_room(sp->get_user(),conn);
        //5.设置会话定时
        _sm.set_session_timer(sp->get_user(),SESSION_FOREVER);
        //6.给客户端响应房间建立完成
        resq_json["optype"] = "room_ready";
        resq_json["result"] = true;
        resq_json["room_id"] = (Json::UInt64)rp->get_room_id();
        resq_json["uid"] = (Json::UInt64)sp->get_user();
        resq_json["white_id"] = (Json::UInt64)rp->get_white_user();
        resq_json["black_id"] = (Json::UInt64)rp->get_black_user();
        return ws_resp(conn,resq_json);
    }
    //websocket长连接建立后的回调函数
    void wsopen_callback(websocketpp::connection_hdl hdl)
    {
        //1.通过hdl获取conn信息
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        //2.通过conn获取到连接req
        websocketpp::http::parser::request req = conn->get_request();
        //3.通过request获取到uri
        std::string uri = req.get_uri();
        if(uri == "/hall")
            return wsopen_game_hall(conn);
        else if(uri == "/room")
            return wsopen_game_room(conn);
        //后期添加观战和人机对战
        // else if(uri == "/watch")
        //     return wsopen_game_watch(conn);
        // else if(uri == "machine")
        //     return wsopen_game_machine(conn);
    }
    //关闭游戏大厅长连接前处理
    void wsclose_game_hall(wsserver_t::connection_ptr& conn)
    {
        //1.获取用户信息
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;
        //2.将该用户从大厅连接中移除
        _ou.exit_game_hall(sp->get_user());
        //3.维护生命周期
        _sm.set_session_timer(sp->get_user(),SESSION_TIMEOUT);
    }
    //关闭游戏房间长连接前处理
    void wsclose_game_room(wsserver_t::connection_ptr& conn)
    {
        //1.获取用户信息
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;
        //2.将其从房间连接中移除
        _ou.exit_game_room(sp->get_user());
        //3.重新设置生命周期
        _sm.set_session_timer(sp->get_user(),SESSION_TIMEOUT);
        //4.删除该用户,当房间中没有用户时该房间会自动销毁
        _rm.remove_player(sp->get_user());
    }
    //websocket长连接断开前前处理
    void wsclose_callback(websocketpp::connection_hdl hdl)
    {
        //获取连接
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        //通过连接获取req
        websocketpp::http::parser::request req = conn->get_request();
        //通过req获取到uri
        std::string uri = req.get_uri();
        if(uri == "/hall")
            return wsclose_game_hall(conn);
        else if(uri == "/room")
            return wsclose_game_room(conn);
        //后期添加观战和人机对战
        // else if(uri == "/watch")
        //     return wsclose_game_watch(conn);
        // else if(uri == "machine")
        //     return wsclose_game_machine(conn);
    }
    //游戏大厅websocket长连接信息处理
    void wsmsg_game_hall(wsserver_t::connection_ptr& conn,wsserver_t::message_ptr msg)
    {
        Json::Value resp_json;
        std::string resp_body;
        //1.获取用户信息
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;
        //2.获取请求信息
        std::string req_body = msg->get_payload();
        Json::Value req_json;
        bool ret = util_json::unserialize(req_body,req_json);
        if(ret == false)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "请求信息解析失败";
            return ws_resp(conn,resp_json);
        }
        //3.对请求信息进行处理(开始对战和停止对战)
        //3.1开始对战
        if(!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
        {
            _mm.add(sp->get_user());
            resp_json["optype"] = "match_start";
            resp_json["result"] = true;
            return ws_resp(conn,resp_json);
        }//3.停止对战
        else if(!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
        {
            _mm.del(sp->get_user());
            resp_json["optype"] = "match_stop";
            resp_json["result"] = true;
            return ws_resp(conn,resp_json);
        }//4.其它未知信息
        resp_json["optype"] = "unknow";
        resp_json["reason"] = "请求类型未知";
        resp_json["result"] = true;
        return ws_resp(conn,resp_json);
    }
    //游戏房间websocket长连接信息处理
    void wsmsg_game_room(wsserver_t::connection_ptr& conn,wsserver_t::message_ptr msg)
    {
        //1.获取用户信息
        Json::Value resq_json;
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;
        //2.验证房间信息
        room_ptr rp = _rm.get_room_by_uid(sp->get_user());
        if(rp.get() == nullptr)
        {
            resq_json["optype"] = "unknow";
            resq_json["reason"] = "没有找到玩家房间信息";
            resq_json["result"] = false;
            return ws_resp(conn,resq_json);
        }
        //3.处理数据
        Json::Value req_json;
        std::string req_body = msg->get_payload();
        bool ret = util_json::unserialize(req_body,req_json);
        if(ret == nullptr)
        {
            resq_json["optype"] = "unknow";
            resq_json["reason"] = "没有找到玩家房间信息";
            resq_json["result"] = false;
            return ws_resp(conn,resq_json);
        }
        return rp->handle_request(req_json);
    }
    //websocket长连接消息处理
    void wsmsg_callback(websocketpp::connection_hdl hdl,wsserver_t::message_ptr msg)
    {
        //获取连接
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        //通过连接获取req
        websocketpp::http::parser::request req = conn->get_request();
        //通过req获取到uri
        std::string uri = req.get_uri();
        if(uri == "/hall")
            return wsmsg_game_hall(conn,msg);
        else if(uri == "/room")
            return wsmsg_game_room(conn,msg);
        //后期添加观战和人机对战
        // else if(uri == "/watch")
        //     return wsclose_game_watch(conn);
        // else if(uri == "machine")
        //     return wsclose_game_machine(conn);
    }
private:
    std::string _web_root; //静态资源根目录
    user_table _ut;
    online_user _ou;
    room_manage _rm;
    matcher_manager _mm;
    session_manager _sm;
    wsserver_t _wsserver;
};